package Border;

import be.kuleuven.cs.som.annotate.Raw;
import SquarePackage.Direction;
import SquarePackage.Rock;
import SquarePackage.Square;
import SquarePackage.TeleportableSquare;

/**
 * Class for a border between 2 squares.
 * @invar Each border must at least have 1 square attached to it and maximum 2 squares attached to it
 * 			| hasProperSquares()
 * 
 * @author 	Brecht Reynders and Florian Hendrickx
 * @version	1.1
 */

public abstract class Border {
	/**
	 * field representing the identifying square
	 */
	private Square firstSquare;
	/**
	 * field representing the second square
	 */
	private Square secondSquare;
	/**
	 * termination state
	 */
	private boolean isTerminated;
	/**
	 * keeps the direction of this border inside its initial square,
	 * if squares are merged this is off course the opposite for the second square added
	 */
	private Direction initialdirection;

	/**
	 * constructor for the Border Class
	 * @param	square
	 * 			the Square the border belongs to.
	 * @post	when the border is created the square will be kept as its identifier, later on a second square can be added to this Border.
	 * 			| new.getIdentifier()==square
	 * @post	if both square		
	 * @throws	IllegalArgumentException
	 * 			If and only if this square is illegal for creating a border.
	 */
	protected Border(Square square,Direction direction){
		this(square,null,direction);
	}
	/**
	 * extended constructor for squares
	 */
	protected Border(Square square1, Square square2,Direction direction){
		setDirection(direction);
		constructBorder(square1,direction);
		if(square2!=null){
			constructBorder(square2,direction.getOpposite());
		}
	}
	/**
	 * method used to construct a border, easier to read the constructors thanks to this method.
	 * @param square
	 * 			the square to be set as one of the borders squares
	 * @post	if the arguments specified are correct, the square is set at the empty position, first the Identifier, but if there is already one, it is put as second square.
	 * @throws	IllegalArgumentException
	 * 			thrown when this border is already terminated or already has two borders or if the given square has a border in the given direction and that border is not this one.
	 * @throws	IllgalArgumentException
	 * 			if we try to set a Border in a square, that does not have a direction opposite to the identifying square's borderdirecton.
	 */	
	@Raw
	private void constructBorder(@Raw Square square, Direction direction) throws IllegalArgumentException {
		if(square!=null && this.getClass()!=EmptySpace.class )
		{
			if(this.isTerminated()||(this.numberOfSquares()==2)||(
					(square.hasBorder(direction))&& (square.getBorderAt(direction)!=this))){
				throw new IllegalArgumentException("this border cannot be attached to this square");
			}
		}
		if((getIdentifier()==null)||(getIdentifier()==square)){
			this.setIdentifier(square);
			if(square!=null && square.getClass()!=Rock.class){
				((TeleportableSquare)square).setBorder(direction, this);	
			}
		}		
		else{
			this.setSecondSquare(square);	
			if( square!=null && square.getClass()!=Rock.class){
				((TeleportableSquare)square).setBorder(direction, this);	

			}
		}
		if(square!=null && !hasProperSquares()){
			throw new IllegalArgumentException("this border does not have proper squares");
		}
	}
	/**
	 * Returns whether this border is isolated.
	 * 
	 * @return 	True if and only if there is non pass troughable object between the squares.
	 */
	public abstract boolean getIsolation();

	/**
	 * Return whether this border can be created(Checker)
	 * 
	 * @return 	True if and only if this border can be created depending on the type of border.
	 */
	protected boolean canHaveAsSquare(Square square,Direction direction){
		if(square==null){
			return true;
		}
		if(!isTerminated()&& (!square.hasBorder(direction)||square.getBorderAt(direction)==this)&& isValidDirection(direction)&& square!=null){
			
			return true;
			}
		return false;
	}
	/**
	 * setter for the square of the border
	 * @param square
	 * 			the new identifier for the square
	 * @post	| this.getIdentifier() = square
	 */
	protected void setIdentifier(Square square) {
		
		if(square==null){
			this.firstSquare = null;
		}
		else {
			if((canHaveAsSquare(square,getDirection())&&((getIdentifier()==null)||(getIdentifier().isTerminated())))){
				this.firstSquare = square;
			}
		}
	}
    /**
     * Returns the other square that is attached to this border.
     * @param       square
     *              The square of which we want to get the neighbour seperated by this border
     * @return      The first square if the given square was the second square attached to this border.
     *                      |if (getIdentifier.equals(square) then result == getSecondSquare()
     * @return      The second square if the given square was the first square attached to this border.
     *                      |if (getSecondSquare.equals(square)) then result == getIdentifier()
     * @throws      IllegalArgumentException
					thrown if the square given is not effective
     * @throws      IllegalArgumentException
     *                      The given square has nothing to do with this Border
     *                      | (square != getSquare1()) && (square != getSquare2())
     */
    public Square getOtherSquare(Square square) {
            if(square == null)
                    throw new IllegalArgumentException("The given square was not effective.");
            if(square != getIdentifier()){
                    if (square != getSecondSquare()){
                        throw new IllegalArgumentException("The given square was not one of the squares attached to this border");
                    }
                    return getIdentifier();
                    }
            return getSecondSquare();
    }
	/**
	 * getter for the square of the border
	 * @return Square
	 * 			the identifying square
	 */
	public Square getIdentifier() {
		return firstSquare;
	}
	/**
	 * setter for the square if we dont know if this is the first square we add or the second one
	 * @param	square
	 * 			the square to set as identifier or as second square
	 * @throws	IllegalArgumentException
	 * 			this happens when a 3rd square is added
	 */
	public void setSquare(Square square) throws IllegalArgumentException{
		if((getIdentifier()==null)||(getIdentifier()==square)){
			this.setIdentifier(square);
			}
		else{
			if(getSecondSquare()!=null && getSecondSquare()!=square){
				throw new IllegalArgumentException("already has two squares attached to it");
			}
			this.setSecondSquare(square);	
		}
	}

	/**
	 * Checker for the direction argument of the constructor
	 * 
	 * @return true if and only if the direction is not null.
	 * 			if(direction!=true)(result=true)
	 */
	protected boolean isValidDirection(Direction direction){
		if(direction != null){
			return true;
		}
		return false;
	}
	/**
	 * setter for direction
	 * @param direction
	 * 			the new direction for the border
	 * @post	the direction is set to the direction specified
	 * 			|new.getDirection()= direction
	 */
	public void setDirection(Direction direction) {
		this.initialdirection = direction;
	}
	/**
	 * getter for direction
	 * @return	the direction of the border
	 */
	public Direction getDirection() {
		return initialdirection;
	}
	/**
	 * destructor
	 * used to terminate an object of the Border class
	 */
	public void terminate(){
		if(!isTerminated()){
			Square square1 = getIdentifier();
			Square square2 = getSecondSquare();
			this.isTerminated=true;
			setIdentifier(null);
			setSecondSquare(null);
			if(square1!=null){
			square1.removeBorder(this);
			}
			if(square2!=null){
			square2.removeBorder(this);
			}
		}
	}
	/**
	 * getter for second square
	 * @return	second square
	 */
	public Square getSecondSquare() {
		return this.secondSquare;
	}
	/**
	 * setter for second square
	 * @param square
	 * 			the new square for second square
	 * @post getSecondSquare()==square
	 */
	protected void setSecondSquare(Square square) {
		if(square==null){
			this.secondSquare = null;
		}
		else if((canHaveAsSquare(square,getDirection().getOpposite()))&&((getSecondSquare()==null)||(getSecondSquare().isTerminated()))){
			this.secondSquare = square;
		}
	}
	/**
	 * get termination state
	 * @return	True if and only if this border is terminated
	 */
	public boolean isTerminated(){
	return this.isTerminated;	
	}
	/**
	 * counts the amount of squares 0..2
	 * @return	Integer between 0 and 2
	 */
	protected int numberOfSquares(){
		int counter = 0;
		if(getIdentifier()!=null){
			counter++;
		}
		if(getSecondSquare()!=null){
			counter++;
		}
		return counter;
	}
	/**
	 * check if a square is attached to a border
	 * @param	square
	 * 			the square to check
	 * @return	True if and only if one of the squares that references border is this square
	 * 			| result (square==firstSquare) ||(square==secondSquare)
	 */
	public boolean hasSquare(Square square){
		return (square==getIdentifier()) ||(square==getSecondSquare());
	}
	/**
	 * demolish a border attached to a given square.
	 * @param	square
	 * 			the square to detach the border from
	 * @post	removes the link between this border and the square given.
	 * 			If the square removed is the only square the border is terminated,
	 * 			if there is a second one, this is set as the new identifying square.
	 */
	public void demolish(Square square){
		if( square!=null && !this.isTerminated() &&
				this.hasSquare(square)&& (square.numberOfBorders(square.getBorders())>1||square.isTerminated())
				&& (square == getSecondSquare() || square == getIdentifier())){
			if(square==getIdentifier()){
				if(getSecondSquare()==null){
					terminate();
				}
				else{
					setIdentifier(getSecondSquare());
					setSecondSquare(null);
					square.removeBorder(this);
				}
			}
			else{
				setSecondSquare(null);
				square.removeBorder(this);
			}
		}
	}
	/**
	 * hasproper method checks if the invariant is true, 
	 * @return true if and only if this border has at least 1 square.
	 */
	protected boolean hasProperSquares(){
		if(numberOfSquares()>=1){
			if(numberOfSquares()==2){
				if(getIdentifier().getBorderDirection(this)!=getSecondSquare().getBorderDirection(this).getOpposite()){
					return false;
				}
			}
			return true;
		}
		return false;
	}
    /**
     * can this type of border be on the floor
     * @return True if and only if this border can be in the floor
     */
    public boolean canBeOnFloor(){
    	return true;
    } 
    /**
     * can border be opened and closed?
     * @return true if and only if this border can be made pass trough
     */
    public boolean canBeOpenedOrClosed(){
    	return false;
    }
	
	
	public abstract Border getHighest(Border border);
	
}
//TODO abstracte methodes?
//TODO TOTAAL PROGRAMMEREN